/*
   我们要讨论的是网络中进程之间如何通信？首要解决的问题是如何唯一标识一个进程，否则通信无从谈起！
   在本地可以通过进程PID来唯一标识一个进程，但是在网络中这是行不通的。其实TCP/IP协议族已经帮我们解决了这个问题，
   网络层的“ip地址”可以唯一标识网络中的主机，而传输层的“协议+端口”可以唯一标识主机中的应用程序（进程）。这样
   利用三元组（ip地址，协议，端口）就可以标识网络的进程了，网络中的进程通信就可以利用这个标志与其它进程进行交互。

   使用TCP/IP协议的应用程序通常采用应用编程接口：
   UNIX  BSD的套接字（socket）和UNIX System V的TLI（已经被淘汰），来实现网络进程之间的通信。
   就目前而言，几乎所有的应用程序都是采用socket，这就是我为什么说“一切皆socket”。

what is socket:
	socket是使用 标准Unix 文件描述符 (file descriptor) 和其它程序通讯的方式。
	unix 程序在执行任何形式的 I/O 的时候，程序是在读或者写一个文件描述符。一个文件描述符只是一个和打开的文件相关联的整数。这个文件可能是一个网络连接，FIFO，管道，终端，磁盘上的文件或者什么其它的东西。
	you can use 系统调用 socket()，它返回套接字描述符 (socket descriptor)，然后你再通过它来进行send() 和 recv()调用。

	不要采用小于 1024的端口号。所有小于1024的端口号都被系统保留 你可以选择从1024 到65535的端口
*/

#include <cstdio>
#include <cstdlib>
#include <string>
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h> 
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h> 
#include <netdb.h> 
#include <fcntl.h>
#include <signal.h>

// server test
int tcpServerTest(int argc, char** argv)
{
	std::cout<<" in func tcpServerTest"<<std::endl;

	/*
	socket函数对应于普通文件的打开操作。
	普通文件的打开操作返回一个文件描述字，而socket()用于创建一个socket描述符,它唯一标识一个socket.
	这个socket描述字跟文件描述字一样，后续的操作都有用到它，把它作为参数，通过它来进行一些读写操作。
	正如可以给fopen的传入不同参数值，以打开不同的文件。
	创建socket的时候，也可以指定不同的参数创建不同的socket描述符，socket函数的三个参数分别为：
		domain：即协议域，又称为协议族（family）。
			常用的协议族有，AF_INET、AF_INET6、AF_LOCAL（或称AF_UNIX，Unix域socket）、AF_ROUTE等等。
			协议族决定了socket的地址类型，在通信中必须采用对应的地址，
			如AF_INET决定了要用ipv4地址（32位的）与端口号（16位的）的组合、
			AF_UNIX决定了要用一个绝对路径名作为地址。
		type：指定socket类型。
			常用的socket类型有，SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等。
		protocol：故名思意，就是指定协议。
			常用的协议有，IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等，
			它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议
	注意：并不是上面的type和protocol可以随意组合的，如SOCK_STREAM不可以跟IPPROTO_UDP组合。
	当protocol为0时，会自动选择type类型对应的默认协议。
	当我们调用socket创建一个socket时，返回的socket描述字它存在于协议族（address family，AF_XXX）空间中，
	但没有一个具体的地址
	 */
	int sfd = socket(AF_INET, SOCK_STREAM, 0);
	if(-1 == sfd)
	{
		perror("fail to create socket file descriptor");
		return -1;
	}

	/*
	struct sockaddr { 
		unsigned short sa_family;	// 地址族， AF_xxx 
		char sa_data[14];			// 14 字节的协议地址 
	}; 
	sa_family一般为AF_INET；sa_data则包含该socket的IP地址和端口号。 
	
	另外还有一种结构类型： 
	struct sockaddr_in { 
		short int sin_family;			// 地址族 
		unsigned short int sin_port;	// 端口号
		struct in_addr sin_addr;		// IP地址
		unsigned char sin_zero[8];		// 填充0 以保持与struct sockaddr同样大小
	}; 
	sin_zero(它用来将sockaddr_in结构填充到与struct sockaddr同样的长度)应该用bzero()或memset()函数将其置为零。
	指向sockaddr_in 的指针和指向sockaddr的指针可以相互转换，
	这意味着如果一个函数所需参数类型是sockaddr时，你可以将一个指向 sockaddr_in的指针转换为指向sockaddr的指针；或者相反。
	sin_family通常被赋AF_INET；
	sin_port和 sin_addr应该转换成为网络字节优先顺序；
	而sin_addr则不需要转换。 
	我们下面讨论几个字节顺序转换函数： 
	htons()--"Host to Network Short" ; htonl()--"Host to Network Long" 
	ntohs()--"Network to Host Short" ; ntohl()--"Network to Host Long" 
	在这里， h表示"host" ，n表示"network"，s 表示"short"，l表示 "long"。
	 */
	const int listenMax = 10;
	const short portnum = 1226;
	struct sockaddr_in saddr;
	saddr.sin_family = AF_INET;
	saddr.sin_port = htons(portnum);
	saddr.sin_addr.s_addr = htonl(INADDR_ANY);
	bzero(&(saddr.sin_zero), sizeof(saddr.sin_zero));

	/*
	bind()函数把一个地址族中的特定地址赋给socket。
	例如对应AF_INET、AF_INET6就是把一个ipv4或ipv6地址和端口号组合赋给socket.
	函数的三个参数分别为：
		sockfd：即socket描述字，它是通过socket()函数创建，唯一标识一个socket。bind()函数就是将给这个描述字绑定一个名字。
		addr：一个const struct sockaddr *指针，指向要绑定给sockfd的协议地址。
			这个地址结构根据地址创建socket时的地址协议族的不同而不同
		addrlen：对应的是地址的长度。

	通常服务器在启动的时候都会绑定一个众所周知的地址（如ip地址+端口号），用于提供服务，客户就可以通过它来接连服务器；
	而客户端就不用指定，有系统自动分配一个端口号和自身的ip地址组合。
	这就是为什么通常服务器端在listen之前会调用bind()，而客户端就不会调用，而是在connect()时由系统随机生成一个。
	 */
	int sbre = bind(sfd, (struct sockaddr*)&saddr, sizeof(struct sockaddr));
	if(-1 == sbre)
	{
		perror("fail to bind socket");
		return -1;
	}

	/*
	如果作为一个服务器，在调用socket()、bind()之后就会调用listen()来监听这个socket，
	如果客户端这时调用connect()发出连接请求，服务器端就会接收到这个请求。
	第一个参数即为要监听的socket描述字，
	第二个参数为相应socket可以排队的最大连接个数。
	socket()函数创建的socket默认是一个主动类型的，listen函数将socket变为被动类型的，等待客户的连接请求。
	 */
	int slre = listen(sfd, listenMax);
	if(-1 == slre)
	{
		perror("fail to call listen");
		return -1;
	}

	int sin_size = sizeof(struct sockaddr_in);
	struct sockaddr_in remotAddr;
	int loop=0;
	while(loop<10)
	{
		++loop;

		/*
		TCP服务器端依次调用socket()、bind()、listen()之后，就会监听指定的socket地址了。
		TCP客户端依次调用socket()、connect()之后就想TCP服务器发送了一个连接请求。
		TCP服务器监听到这个请求之后，就会调用accept()函数取接收请求，这样连接就建立好了。

		第一个参数为服务器的socket描述字，
		第二个参数为指向struct sockaddr *的指针，用于返回客户端的协议地址，
		第三个参数为协议地址的长度。
		
		如果accpet成功，那么其返回值是由内核自动生成的一个全新的描述字，代表与返回客户的TCP连接。
		注意：
			accept的第一个参数为服务器的socket描述字，是服务器开始调用socket()函数生成的，称为监听socket描述字；
			而accept函数返回的是已连接的socket描述字。
			一个服务器通常通常仅仅只创建一个监听socket描述字，它在该服务器的生命周期内一直存在。
			内核为每个由服务器进程接受的客户连接创建了一个已连接socket描述字，
			当服务器完成了对某个客户的服务，相应的已连接socket描述字就被关闭。
		 */
		int remoteSfd= accept(sfd, (struct sockaddr*)&remotAddr, (socklen_t *)&sin_size);
		if(-1 == remoteSfd)
		{
			perror("fail to call accept");
			return -1;
		}
		
		std::cout<<"loop="<<loop<<" accept input"<<std::endl;
		char buf[1024];
		bzero(buf,1024);
		int rcfre = recv(remoteSfd, buf, 1024, 0);
		if(-1 == rcfre)
		{
			perror("fail to call recv");
			return -1;
		}
		std::cout<<"using tcp, recv from "<<inet_ntoa(*((struct in_addr *)&(remotAddr.sin_addr)))<<":"<<buf<<std::endl;

		char* msg2remote = "service alive";
		int msglen = strlen(msg2remote);
		int bytesout = send(remoteSfd, msg2remote, msglen, 0);
		if(-1 == bytesout)
		{
			perror("fail to call send to tell remote 'service alive'");
			return -1;
		}

		std::cout<<bytesout<<" bytes rsp send "<<std::endl;
		close(remoteSfd);
	}
	close(sfd);

	return 0;
}

int udpServerTest(int argc, char** argv)
{
	std::cout<<" in func udpServerTest"<<std::endl;

	int sfd = socket(AF_INET, SOCK_DGRAM, 0);
	if(-1 == sfd)
	{
		perror("fail to create socket file descriptor");
		return -1;
	}

	const short portnum = 1225;
	struct sockaddr_in saddr;
	saddr.sin_family = AF_INET;
	saddr.sin_port = htons(portnum);
	saddr.sin_addr.s_addr = htonl(INADDR_ANY);
	bzero(&(saddr.sin_zero), sizeof(saddr.sin_zero));

	int sbre = bind(sfd, (struct sockaddr*)&saddr, sizeof(struct sockaddr));
	if(-1 == sbre)
	{
		perror("in udpServerTest fail to bind socket");
		return -1;
	}

	/* when using udp, listen is not necessary
	int slre = listen(sfd, listenMax);
	if(-1 == slre)
	{
		perror("fail to call listen");
		return -1;
	}
	*/

	int sin_size = sizeof(struct sockaddr_in);
	struct sockaddr_in remotAddr;
	int loop=0;
	while(loop<10)
	{
		++loop;
		/* when using udp, accept is not necessary
		int remoteSfd= accept(sfd, (struct sockaddr*)&remotAddr, (socklen_t *)&sin_size);
		if(-1 == remoteSfd)
		{
			perror("fail to call accept");
			return -1;
		}
		std::cout<<"loop="<<loop<<" accept input"<<std::endl;
		*/
		char buf[1024];
		bzero(buf,1024);
		int rcfre = recvfrom(sfd, buf, 1024, 0, (struct sockaddr*)&remotAddr, (socklen_t *)&sin_size);
		if(-1 == rcfre)
		{
			perror("fail to call recv");
			return -1;
		}
		std::cout<<"loop "<<loop<<", using udp, recv from "<<inet_ntoa(*((struct in_addr *)&(remotAddr.sin_addr)))<<":"<<buf<<std::endl;

		char* msg2remote = "service alive";
		int msglen = strlen(msg2remote);
		int bytesout = sendto(sfd, msg2remote, msglen, 0, (struct sockaddr*)&remotAddr, sizeof(struct sockaddr));
		if(-1 == bytesout)
		{
			perror("fail to call send to tell remote 'service alive'");
			return -1;
		}

		std::cout<<bytesout<<" bytes rsp send "<<std::endl;
	}
	close(sfd);

	return 0;
}

int main(int argc, char** argv)
{
	char hostname[128];
	gethostname(hostname, 128);
	std::cout<<std::endl<<hostname<<std::endl;

	struct hostent * tmphp = gethostbyname(hostname);
	if(NULL != tmphp)
	{
		std::cout<<"Host name : "<<tmphp->h_name<<std::endl;
		std::cout<<"IP Address : "<<inet_ntoa(*((struct in_addr *)tmphp->h_addr))<<std::endl;
	}
	
	int pid = fork();
	if(-1 == pid)
	{
		std::cout<<"fail to call fork"<<std::endl;
		return 0;	
	}
	else if(0 == pid)
	{
		// child process
		int testre = tcpServerTest(argc, argv);
		std::cout<<"tcpServerTest return "<<testre<<std::endl;
		return 0;
	}
	else
	{
		// parent process
		int testre = udpServerTest(argc, argv);
		std::cout<<"udpServerTest return "<<testre<<std::endl;
		kill(pid, SIGKILL);
		return 0;
	}
}
